
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Diagnostics;
using System.Threading;
using System.Xml; 

using Swaf;
using Swaf.Call;
using Swaf.Config;
using Swaf.Container;
using Swaf.BizRule;

namespace Swaf.BizRule.ThreadedQueue
{
	/// <summary> An internal class used by <c>BasicRuleQueue</c> that maintains the rule call
	/// info and sits in the work queue until a work thread can process the rule from this
	/// object.
	/// </summary>
	class QueueElement : IRunningRule
	{
		internal long m_ruleId = 0;
		internal ICallResults ruleInfo;
		internal object[] ruleParams;
		internal ICall m_callback;
		internal IRuleContext context;
		internal IRuleGroup ruleGroup;
		internal LinkedList.NodeRef m_rulesetQueueRef;
		//MJD5: internal LinkedList.NodeRef m_mainRuleQueueRef;

		protected IRuleInfo m_ruleInfo;
		protected RuleQueue m_queue;
		protected static long s_nextRuleId = 0;
		protected IRunningRule m_parentRule = null;

		public LinkedList.NodeRef RulesetQueuePos
		{
			get{return m_rulesetQueueRef;}
			set{m_rulesetQueueRef = value;}
		}

		/*MJD5:
		public LinkedList.NodeRef MainRuleQueuePos
		{
			get{return m_mainRuleQueueRef;}
			set{m_mainRuleQueueRef = value;}
		}
		*/

		internal QueueElement(RuleQueue q, IRuleInfo ruleInfo, ICallResults callInfo, IRuleContext context, IRuleGroup ruleGroup, object ruleParams)
			:this(q, ruleInfo, callInfo, context, ruleGroup, null, ruleParams)
		{
		}

		internal QueueElement(RuleQueue q, IRuleInfo ruleInfo, ICallResults callInfo, IRuleContext context, IRuleGroup ruleGroup, ICall callback, object ruleParams)
			:this(q, ruleInfo, callInfo, context, ruleGroup, callback, ruleParams, true)
		{
		}

		internal QueueElement(RuleQueue q, IRuleInfo ruleInfo, ICallResults callInfo, IRuleContext context, IRuleGroup ruleGroup, ICall callback, object ruleParams, bool addRuleToInitialRequest)
			{
			m_ruleId = Interlocked.Increment(ref s_nextRuleId);
			m_queue = q;
			this.ruleInfo = callInfo;
			m_ruleInfo = ruleInfo;
			this.context = context;
			this.ruleGroup = ruleGroup;
			m_callback = callback;
			if(ruleParams is object[])
			{
				object[] origParams = (object[])ruleParams;
				this.ruleParams = new object[origParams.Length +2];
				Array.Copy(origParams, 0, this.ruleParams, 2, origParams.Length);
			}
			else
			{
				this.ruleParams = new object[3];
				this.ruleParams[2] = ruleParams;
			}

			IRuleMonitor monitor = m_queue.Monitor;
			if(monitor != null)
				monitor.ruleEnqueued(DateTime.Now, (int)m_ruleId, false, callInfo.CallInfo.Id, ruleParams);
		}
		
		public void execute(Worker w)
		{
			Debug.Assert(w != null);
			m_parentRule = w.m_currentRunningRule;
			w.m_currentRunningRule = this;

			IRuleMonitor monitor = m_queue.Monitor;
			//ruleGroup.state = CallState.CallExecuting;

			IApplication oldApp = Application.currentApp;

			try
			{
				object ruleReturn = null;
				Exception ruleException  = null;

				try 
				{
					this.ruleParams[0] = context.App;
					this.ruleParams[1] = context;

					bool executeRule = true;
					Application.currentApp = context.App;
					if(monitor != null)
						monitor.ruleStarted(DateTime.Now, (int)m_ruleId, ruleInfo.CallInfo.Id, ruleParams);
					if(m_ruleInfo.hasStandinDecorators)
						foreach(IDecorator d in m_ruleInfo.Decorators.Values)
						{
							//We can safely assume it is also a rule decorator because we test
							//for this when the rule is being defined.
							if(d.IsStandin && !((IRuleDecorator)d).preRuleExecution(this))
								executeRule = false;
						}

					if(executeRule)
					{
						ruleReturn = ruleInfo.CallInfo.call(ruleParams);
					}
					if(monitor != null)
						monitor.ruleCompleted(DateTime.Now, (int)m_ruleId, ruleInfo.CallInfo.Id);
				}
				catch(Exception e)
				{
					ruleException = e;
					if(monitor != null)
						monitor.ruleFailed(DateTime.Now, (int)m_ruleId, ruleInfo.CallInfo.Id, e);
				}

				try
				{
					if(m_ruleInfo.hasStandinDecorators)
						foreach(IDecorator d in m_ruleInfo.Decorators.Values)
							if(d.IsStandin)
								((IRuleDecorator)d).postRuleExecution(this, ref ruleReturn, ref ruleException);
					ruleInfo.setResults(ruleReturn, ruleException);
				}
				finally
				{
					((SingleCallAndResult)ruleInfo).setCallInfo(null);
				//ruleGroup.state = CallState.CallComplete;
					if(ruleGroup != null)
						((IRuleGroupControl)ruleGroup).ruleCompleted();
				}
				if (m_callback != null)
					try
					{
						m_callback.call(ruleInfo);
					}
					catch (Exception)
					{
						context.App.Log.println(Application.DEFAULT_FRAMEWORK_LOG_LEVEL,context.App.resMgr.getMessage("BusinessRules.Rules.CallbackFailure", ruleInfo.CallInfo.Id, m_callback.ToString()));
					}
			}
			finally
			{
				Application.currentApp = oldApp;
				w.m_currentRunningRule = m_parentRule;
				m_parentRule = null;
			}
		}

		internal IRunningRule CurrentRule
		{
			get
			{
				return this;
			}
		}
		#region IRunningRule Members

		public long UniqueId
		{
			get
			{
				return m_ruleId;
			}
		}

		public string decoratorValue(string decoratorName, string defaultValue)
		{
			try
			{
				if (m_ruleInfo.Decorators.Contains(decoratorName))
				{
					string res = ((IDecorator)m_ruleInfo.Decorators[decoratorName]).Value;

					return (res == null || res == string.Empty)? defaultValue : res;
				}
				else
					return defaultValue;
			}
			catch(Exception)
			{
				return defaultValue;
			}
		}

		public string decoratorValue(string decoratorName)
		{
			return decoratorValue(decoratorName, null);
		}

		public object this[string name]
		{
			get
			{
				return context.get(name);
			}
			set
			{
				if(value != null)
					context.put(name, value);
				else
					context.put(name, null, "remove");
			}
		}

		public IRuleInfo Info
		{
			get
			{
				return m_ruleInfo;
			}
		}

		public IRunningRule Parent{get{return m_parentRule;}}
		public object[] inputParams{get{return ruleParams;}}
		#endregion
	}
}